package cn.mujiankeji.mbrowser.功能.广告拦截.项目


import cn.mujiankeji.mbrowser.功能.广告拦截.Adblock规则解析操作
import cn.mujiankeji.mbrowser.功能.广告拦截.广告拦截操作
import cn.mujiankeji.mbrowser.数据.配置项.应用配置
import com.blankj.utilcode.util.FileIOUtils
import com.blankj.utilcode.util.FileUtils
import kotlinx.serialization.json.Json
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write

/**
 * 对应着一个广告过滤文件，  sign 为 local 则本地
 * by Jen. 2020.10.
 * 修改: 使用JSON存储替代数据库
 */
class 广告拦截订阅文件详情(val 文件名称 : String = "local"){

    // 读写锁
    private val 读写锁 = ReentrantReadWriteLock()


    // JSON序列化配置
    private val json = Json { 
        prettyPrint = true 
        isLenient = true
        ignoreUnknownKeys = true
    }


    /**
     * 将 资源拦截、元素隐藏 黑白名单 区分4个文件，有序列表
     */
    var 拦截地址 : MutableList<广告拦截规则信息> = ArrayList()
    var 白名单地址 : MutableList<广告拦截规则信息> = ArrayList()
    var 拦截元素 : MutableList<广告拦截规则信息> = ArrayList()
    var 白名单元素 : MutableList<广告拦截规则信息> = ArrayList()

    // 存储文件夹路径
    val 规则对象文件夹 : String = 应用配置.路径_广告过滤规则 + "${文件名称}/"
    val 规则文本文件路径  =  应用配置.路径_广告过滤规则 + "${文件名称}.txt"

    // 初始化规则文件
    init {
        读写锁.write {
            if (!加载()){
                从文本规则文件导入(规则文本文件路径)
            }
        }
    }

    fun size() : Int{
        return 拦截地址.size + 白名单地址.size + 拦截元素.size + 白名单元素.size
    }

    private fun 加载() : Boolean{
        try {
            if (!FileUtils.isFileExists(规则对象文件夹 + "r.json")) return false
            
            拦截地址 = json.decodeFromString<List<广告拦截规则信息>>(
                FileIOUtils.readFile2String(规则对象文件夹 + "r.json") ?: "[]"
            ).toMutableList()
            
            白名单地址 = json.decodeFromString<List<广告拦截规则信息>>(
                FileIOUtils.readFile2String(规则对象文件夹 + "wr.json") ?: "[]"
            ).toMutableList()
            
            拦截元素 = json.decodeFromString<List<广告拦截规则信息>>(
                FileIOUtils.readFile2String(规则对象文件夹 + "e.json") ?: "[]"
            ).toMutableList()
            
            白名单元素 = json.decodeFromString<List<广告拦截规则信息>>(
                FileIOUtils.readFile2String(规则对象文件夹 + "we.json") ?: "[]"
            ).toMutableList()
            
            return true
        } catch (e : Exception){
            e.printStackTrace()
            return false
        }
    }
    private fun 存储() : Boolean{
        try {
            FileUtils.createOrExistsDir(规则对象文件夹)
            FileIOUtils.writeFileFromString(规则对象文件夹 + "r.json", json.encodeToString(拦截地址))
            FileIOUtils.writeFileFromString(规则对象文件夹 + "wr.json", json.encodeToString(白名单地址))
            FileIOUtils.writeFileFromString(规则对象文件夹 + "e.json", json.encodeToString(拦截元素))
            FileIOUtils.writeFileFromString(规则对象文件夹 + "we.json", json.encodeToString(白名单元素))
            return true
        }catch (e : Exception){
            e.printStackTrace()
            return false
        }
    }


    /**
     * 从文本规则文件导入规则
     * 将文本格式的规则解析并加载到内存中
     * 
     * @param path 规则文件路径
     */
    fun 从文本规则文件导入(path : String){
        FileUtils.delete(规则对象文件夹)
        拦截地址.clear()
        白名单地址.clear()
        拦截元素.clear()
        白名单元素.clear()

        val file = File(path)
        if (file.exists()) {
            FileIOUtils.readFile2List(file).forEach { line ->
                if (!line.isNullOrBlank()) {
                    广告拦截操作.解析(line) { adRule ->
                        添加规则(adRule,line,false)
                    }
                }
            }
        }
        
        存储()
    }


    /**
     * 添加规则 并保存
     */
    fun 添加规则(规则文本 : String){
        广告拦截操作.解析(规则文本){ 规则对象->
            添加规则(规则对象,规则文本)
        }
    }

    fun 添加规则(规则对象 : 广告拦截规则信息, 规则文本 : String, 存储记录 : Boolean = true){
        读写锁.write {
            var l = 拦截地址
            when (规则对象.w) {
                Adblock规则解析操作.隐藏元素 -> l = 拦截元素
                Adblock规则解析操作.白名单元素 -> l = 白名单元素
                Adblock规则解析操作.白名单地址 -> l = 白名单地址
                else -> l = 拦截地址
            }

            // 去重匹配
            if (取匹配项目(l, 规则对象) == null) {

                // 添加到内存
                添加规则(l,规则对象)

                if (!存储记录) return

                // 添加到本地文本文件
                var code = FileIOUtils.readFile2String(File(规则文本文件路径))?:""
                code += "\n" + 规则文本
                FileIOUtils.writeFileFromString(规则文本文件路径, code)

                // 添加到本地解析对象
                存储()
            }
        }

    }

    fun 添加规则(list : MutableList<广告拦截规则信息>, ad : 广告拦截规则信息){
        var left = 0
        var right = list.size - 1
        if (list.size == 0) list.add(ad)
        else if (ad.k <= list[left].k){
            list.add(0,ad)
        }
        else if (ad.k >= list[right].k) {
            list.add(ad)
        }
        else {
            while(left <= right){
                val mid = (right + left) / 2
                // left == right 上下不就
                if (left == right){
                    if (ad.k > list[mid].k) list.add(mid+1,ad)
                    else list.add(mid,ad)
                    break
                }
                else if (list[mid].k == ad.k){
                    list.add(mid,ad)
                    break
                }
                else if (list[mid].k < ad.k){
                    left = mid + 1
                }else if (list[mid].k > ad.k){
                    right = mid - 1
                }
            }
        }
    }
    fun 删除规则(list : MutableList<广告拦截规则信息>, target: 广告拦截规则信息){
        // 0 未分配到key
        if (target.k == 0L){
            for (it in list) {
                // 超出
                if (it.k != 0L) return
                if (是否匹配(it,target)){
                    list.remove(it)
                    删除规则(list, target)  // 并入时并没有去重 因此需重复确认
                    return
                }
            }
            return
        }

        var left = 0
        var right = list.size - 1

        if (list.size == 0 || target.k > list[right].k) return
        while(left <= right){
            val mid : Int = (left+right) / 2
            if (target.k == list[mid].k){
                if (是否匹配(list[mid],target)){
                    list.removeAt(mid)
                    删除规则(list, target)  // 并入时并没有去重
                }

                var id = mid

                // 向前
                while (--id >=0 && list[id].k == target.k){
                    if (是否匹配(list[id],target)){
                        list.removeAt(id)
                        删除规则(list, target)  // 并入时并没有去重
                    }
                }

                // 向后
                id = mid
                while (++id < list.size && list[id].k == target.k) {
                    if (是否匹配(list[id],target)){
                        list.removeAt(id)
                        删除规则(list, target)  // 并入时并没有去重
                    }
                }
                break
            }
            else if (list[mid].k < target.k){
                left = mid + 1
            }else if (list[mid].k > target.k){
                right = mid - 1
            }
        }
        return
    }

    /**
     * 判断两个规则是否匹配
     */
    fun 是否匹配(g1 : 广告拦截规则信息, g2 : 广告拦截规则信息) : Boolean{
        return g1.k == g2.k &&
                g1.r == g2.r &&
                g1.e == g2.e &&
                g1.g == g2.g &&
                g1.gn == g2.gn
    }



    /**
     * 有文件删除，更快
     */
    fun 删除(ad : 广告拦截规则信息){
        读写锁.write {
            var l = 拦截地址
            when(ad.w){
                Adblock规则解析操作.隐藏元素 -> l = 拦截元素
                Adblock规则解析操作.白名单元素 -> l = 白名单元素
                Adblock规则解析操作.白名单地址 -> l = 白名单地址
            }
            删除规则(l, ad)
            存储()
        }
    }

    fun 删除所有(){
        读写锁.write {
            FileUtils.delete(规则对象文件夹)
            拦截地址.clear()
            白名单地址.clear()
            拦截元素.clear()
            白名单元素.clear()
        }
    }

    /**
     * 从 比对源 中匹配 比对目标，若比对成功，返回 比对源中的项目
     * 此比对本地项目可以忽略再次拆分解析项目直接取去完成的解析项目
     */
    fun 取匹配项目(比对源 : MutableList<广告拦截规则信息>, 比对目标: 广告拦截规则信息) : 广告拦截规则信息?{
        var left = 0
        var right = 比对源.size - 1
        if (比对源.size == 0 || 比对目标.k == 0L || 比对目标.k > 比对源[right].k) return null
        while(left <= right){
            val mid : Int = (left+right) / 2
            if (比对目标.k == 比对源[mid].k){
                if (是否匹配(比对源[mid],比对目标)) return 比对源[mid]

                var id = mid

                // 向前
                while (--id >=0 && 比对源[id].k == 比对目标.k)
                    if (是否匹配(比对源[id],比对目标)) return 比对源[id]

                // 向后
                id = mid
                while (++id < 比对源.size && 比对源[id].k == 比对目标.k)
                    if (是否匹配(比对源[id],比对目标)) return 比对源[id]

                break
            }else if (比对源[mid].k < 比对目标.k){
                left = mid + 1
            }else if (比对源[mid].k > 比对目标.k){
                right = mid - 1
            }
        }
        return null
    }

}